home *** CD-ROM | disk | FTP | other *** search
/ CrystalVision Software Se… Wiki Wonder - Wikipedia / CrystalVision Software Services 703: The Wiki Wonder - Wikipedia.iso / 0703 / Educate / Complete Calc / Setup.exe / lib / tcl / http2.5 / http.tcl next >
Encoding:
Text File  |  2006-10-25  |  13.9 KB  |  693 lines

  1.  
  2.  
  3. package require Tcl 8.4
  4. package provide http 2.5.2
  5.  
  6. namespace eval http {
  7. variable http
  8. array set http {
  9. -accept */*
  10. -proxyhost {}
  11. -proxyport {}
  12. -proxyfilter http::ProxyRequired
  13. -urlencoding utf-8
  14. }
  15. set http(-useragent) "Tcl http client package [package provide http]"
  16.  
  17. proc init {} {
  18. for {set i 0} {$i <= 256} {incr i} {
  19. set c [format %c $i]
  20. if {![string match {[-._~a-zA-Z0-9]} $c]} {
  21. set map($c) %[format %.2x $i]
  22. }
  23. }
  24. array set map { " " + \n %0d%0a }
  25. variable formMap [array get map]
  26. }
  27. init
  28.  
  29. variable urlTypes
  30. array set urlTypes {
  31. http    {80 ::socket}
  32. }
  33.  
  34. variable encodings [string tolower [encoding names]]
  35. variable defaultCharset "iso8859-1"
  36.  
  37. namespace export geturl config reset wait formatQuery register unregister
  38. }
  39.  
  40.  
  41. proc http::register {proto port command} {
  42. variable urlTypes
  43. set urlTypes($proto) [list $port $command]
  44. }
  45.  
  46.  
  47. proc http::unregister {proto} {
  48. variable urlTypes
  49. if {![info exists urlTypes($proto)]} {
  50. return -code error "unsupported url type \"$proto\""
  51. }
  52. set old $urlTypes($proto)
  53. unset urlTypes($proto)
  54. return $old
  55. }
  56.  
  57.  
  58. proc http::config {args} {
  59. variable http
  60. set options [lsort [array names http -*]]
  61. set usage [join $options ", "]
  62. if {[llength $args] == 0} {
  63. set result {}
  64. foreach name $options {
  65. lappend result $name $http($name)
  66. }
  67. return $result
  68. }
  69. set options [string map {- ""} $options]
  70. set pat ^-([join $options |])$
  71. if {[llength $args] == 1} {
  72. set flag [lindex $args 0]
  73. if {[regexp -- $pat $flag]} {
  74. return $http($flag)
  75. } else {
  76. return -code error "Unknown option $flag, must be: $usage"
  77. }
  78. } else {
  79. foreach {flag value} $args {
  80. if {[regexp -- $pat $flag]} {
  81. set http($flag) $value
  82. } else {
  83. return -code error "Unknown option $flag, must be: $usage"
  84. }
  85. }
  86. }
  87. }
  88.  
  89.  
  90. proc http::Finish { token {errormsg ""} {skipCB 0}} {
  91. variable $token
  92. upvar 0 $token state
  93. global errorInfo errorCode
  94. if {[string length $errormsg] != 0} {
  95. set state(error) [list $errormsg $errorInfo $errorCode]
  96. set state(status) error
  97. }
  98. catch {close $state(sock)}
  99. catch {after cancel $state(after)}
  100. if {[info exists state(-command)] && !$skipCB} {
  101. if {[catch {eval $state(-command) {$token}} err]} {
  102. if {[string length $errormsg] == 0} {
  103. set state(error) [list $err $errorInfo $errorCode]
  104. set state(status) error
  105. }
  106. }
  107. if {[info exists state(-command)]} {
  108. unset state(-command)
  109. }
  110. }
  111. }
  112.  
  113.  
  114. proc http::reset { token {why reset} } {
  115. variable $token
  116. upvar 0 $token state
  117. set state(status) $why
  118. catch {fileevent $state(sock) readable {}}
  119. catch {fileevent $state(sock) writable {}}
  120. Finish $token
  121. if {[info exists state(error)]} {
  122. set errorlist $state(error)
  123. unset state
  124. eval ::error $errorlist
  125. }
  126. }
  127.  
  128.  
  129. proc http::geturl { url args } {
  130. variable http
  131. variable urlTypes
  132. variable defaultCharset
  133.  
  134.  
  135. if {![info exists http(uid)]} {
  136. set http(uid) 0
  137. }
  138. set token [namespace current]::[incr http(uid)]
  139. variable $token
  140. upvar 0 $token state
  141. reset $token
  142.  
  143.  
  144. array set state {
  145. -binary        false
  146. -blocksize     8192
  147. -queryblocksize 8192
  148. -validate     0
  149. -headers     {}
  150. -timeout     0
  151. -type           application/x-www-form-urlencoded
  152. -queryprogress    {}
  153. state        header
  154. meta        {}
  155. coding        {}
  156. currentsize    0
  157. totalsize    0
  158. querylength    0
  159. queryoffset    0
  160. type            text/html
  161. body            {}
  162. status        ""
  163. http            ""
  164. }
  165. array set type {
  166. -binary        boolean
  167. -blocksize    integer
  168. -queryblocksize integer
  169. -validate    boolean
  170. -timeout    integer
  171. }
  172. set state(charset)    $defaultCharset
  173. set options {-binary -blocksize -channel -command -handler -headers  -progress -query -queryblocksize -querychannel -queryprogress -validate -timeout -type}
  174. set usage [join $options ", "]
  175. set options [string map {- ""} $options]
  176. set pat ^-([join $options |])$
  177. foreach {flag value} $args {
  178. if {[regexp $pat $flag]} {
  179. if {[info exists type($flag)] &&  ![string is $type($flag) -strict $value]} {
  180. unset $token
  181. return -code error "Bad value for $flag ($value), must be $type($flag)"
  182. }
  183. set state($flag) $value
  184. } else {
  185. unset $token
  186. return -code error "Unknown option $flag, can be: $usage"
  187. }
  188. }
  189.  
  190.  
  191. set isQueryChannel [info exists state(-querychannel)]
  192. set isQuery [info exists state(-query)]
  193. if {$isQuery && $isQueryChannel} {
  194. unset $token
  195. return -code error "Can't combine -query and -querychannel options!"
  196. }
  197.  
  198.  
  199.  
  200. set URLmatcher {(?x)        # this is _expanded_ syntax
  201. ^
  202. (?: (\w+) : ) ?            # <protocol scheme>
  203. (?: //
  204. (?:
  205. (
  206. [^@/\#?]+        # <userinfo part of authority>
  207. ) @
  208. )?
  209. ( [^/:\#?]+ )        # <host part of authority>
  210. (?: : (\d+) )?        # <port part of authority>
  211. )?
  212. ( / [^\#?]* (?: \? [^\#?]* )?)?    # <path> (including query)
  213. (?: \# (.*) )?            # <fragment>
  214. $
  215. }
  216.  
  217. if {![regexp -- $URLmatcher $url -> proto user host port srvurl]} {
  218. unset $token
  219. return -code error "Unsupported URL: $url"
  220. }
  221. if {$host eq ""} {
  222. unset $token
  223. return -code error "Missing host part: $url"
  224. }
  225. if {$port ne "" && $port>65535} {
  226. unset $token
  227. return -code error "Invalid port number: $port"
  228. }
  229. if {$user ne ""} {
  230. set validityRE {(?xi)
  231. ^
  232. (?: [-\w.~!$&'()*+,;=:] | %[0-9a-f][0-9a-f] )+
  233. $
  234. }
  235. if {![regexp -- $validityRE $user]} {
  236. unset $token
  237. if {[regexp {(?i)%(?![0-9a-f][0-9a-f]).?.?} $user bad]} {
  238. return -code error  "Illegal encoding character usage \"$bad\" in URL user"
  239. }
  240. return -code error "Illegal characters in URL user"
  241. }
  242. }
  243. if {$srvurl ne ""} {
  244. set validityRE {(?xi)
  245. ^
  246. (?:          [-\w.~!$&'()*+,;=:@/]  | %[0-9a-f][0-9a-f] )*
  247. (?: \? (?: [-\w.~!$&'()*+,;=:@/?] | %[0-9a-f][0-9a-f] )* )?
  248. $
  249. }
  250. if {![regexp -- $validityRE $srvurl]} {
  251. unset $token
  252. if {[regexp {(?i)%(?![0-9a-f][0-9a-f])..} $srvurl bad]} {
  253. return -code error  "Illegal encoding character usage \"$bad\" in URL path"
  254. }
  255. return -code error "Illegal characters in URL path"
  256. }
  257. } else {
  258. set srvurl /
  259. }
  260. if {[string length $proto] == 0} {
  261. set proto http
  262. set url ${proto}:$url
  263. }
  264. if {![info exists urlTypes($proto)]} {
  265. unset $token
  266. return -code error "Unsupported URL type \"$proto\""
  267. }
  268. set defport [lindex $urlTypes($proto) 0]
  269. set defcmd [lindex $urlTypes($proto) 1]
  270.  
  271. if {[string length $port] == 0} {
  272. set port $defport
  273. }
  274. if {![catch {$http(-proxyfilter) $host} proxy]} {
  275. set phost [lindex $proxy 0]
  276. set pport [lindex $proxy 1]
  277. }
  278.  
  279. set url ${proto}://
  280. if {$user ne ""} {
  281. append url $user
  282. append url @
  283. }
  284. append url $host
  285. if {$port != $defport} {
  286. append url : $port
  287. }
  288. append url $srvurl
  289. set state(url) $url
  290.  
  291.  
  292. if {$state(-timeout) > 0} {
  293. set state(after) [after $state(-timeout)  [list http::reset $token timeout]]
  294. set async -async
  295. } else {
  296. set async ""
  297. }
  298.  
  299.  
  300. if {[info exists phost] && [string length $phost]} {
  301. set srvurl $url
  302. set conStat [catch {eval $defcmd $async {$phost $pport}} s]
  303. } else {
  304. set conStat [catch {eval $defcmd $async {$host $port}} s]
  305. }
  306.  
  307. if {$conStat} {
  308. Finish $token "" 1
  309. cleanup $token
  310. return -code error $s
  311. }
  312. set state(sock) $s
  313.  
  314.  
  315. if {$state(-timeout) > 0} {
  316. fileevent $s writable [list http::Connect $token]
  317. http::wait $token
  318.  
  319. if {$state(status) eq "error"} {
  320. set err [lindex $state(error) 0]
  321. cleanup $token
  322. return -code error $err
  323. } elseif {$state(status) ne "connect"} {
  324. return $token
  325. }
  326. set state(status) ""
  327. }
  328.  
  329.  
  330. fconfigure $s -translation {auto crlf} -buffersize $state(-blocksize)
  331.  
  332.  
  333. catch {fconfigure $s -blocking off}
  334. set how GET
  335. if {$isQuery} {
  336. set state(querylength) [string length $state(-query)]
  337. if {$state(querylength) > 0} {
  338. set how POST
  339. set contDone 0
  340. } else {
  341. unset state(-query)
  342. set isQuery 0
  343. }
  344. } elseif {$state(-validate)} {
  345. set how HEAD
  346. } elseif {$isQueryChannel} {
  347. set how POST
  348. fconfigure $state(-querychannel) -blocking 1 -translation binary
  349. set contDone 0
  350. }
  351.  
  352. if {[catch {
  353. puts $s "$how $srvurl HTTP/1.0"
  354. puts $s "Accept: $http(-accept)"
  355. if {$port == $defport} {
  356. puts $s "Host: $host"
  357. } else {
  358. puts $s "Host: $host:$port"
  359. }
  360. puts $s "User-Agent: $http(-useragent)"
  361. foreach {key value} $state(-headers) {
  362. set value [string map [list \n "" \r ""] $value]
  363. set key [string trim $key]
  364. if {$key eq "Content-Length"} {
  365. set contDone 1
  366. set state(querylength) $value
  367. }
  368. if {[string length $key]} {
  369. puts $s "$key: $value"
  370. }
  371. }
  372. if {$isQueryChannel && $state(querylength) == 0} {
  373.  
  374. set start [tell $state(-querychannel)]
  375. seek $state(-querychannel) 0 end
  376. set state(querylength)  [expr {[tell $state(-querychannel)] - $start}]
  377. seek $state(-querychannel) $start
  378. }
  379.  
  380.  
  381. if {$isQuery || $isQueryChannel} {
  382. puts $s "Content-Type: $state(-type)"
  383. if {!$contDone} {
  384. puts $s "Content-Length: $state(querylength)"
  385. }
  386. puts $s ""
  387. fconfigure $s -translation {auto binary}
  388. fileevent $s writable [list http::Write $token]
  389. } else {
  390. puts $s ""
  391. flush $s
  392. fileevent $s readable [list http::Event $token]
  393. }
  394.  
  395. if {! [info exists state(-command)]} {
  396.  
  397. wait $token
  398. if {$state(status) eq "error"} {
  399. return -code error [lindex $state(error) 0]
  400. }
  401. }
  402. } err]} {
  403.  
  404.  
  405. if {$state(status) eq "error"} {
  406. Finish $token $err 1
  407. }
  408. cleanup $token
  409. return -code error $err
  410. }
  411.  
  412. return $token
  413. }
  414.  
  415.  
  416. proc http::data {token} {
  417. variable $token
  418. upvar 0 $token state
  419. return $state(body)
  420. }
  421. proc http::status {token} {
  422. variable $token
  423. upvar 0 $token state
  424. return $state(status)
  425. }
  426. proc http::code {token} {
  427. variable $token
  428. upvar 0 $token state
  429. return $state(http)
  430. }
  431. proc http::ncode {token} {
  432. variable $token
  433. upvar 0 $token state
  434. if {[regexp {[0-9]{3}} $state(http) numeric_code]} {
  435. return $numeric_code
  436. } else {
  437. return $state(http)
  438. }
  439. }
  440. proc http::size {token} {
  441. variable $token
  442. upvar 0 $token state
  443. return $state(currentsize)
  444. }
  445.  
  446. proc http::error {token} {
  447. variable $token
  448. upvar 0 $token state
  449. if {[info exists state(error)]} {
  450. return $state(error)
  451. }
  452. return ""
  453. }
  454.  
  455.  
  456. proc http::cleanup {token} {
  457. variable $token
  458. upvar 0 $token state
  459. if {[info exists state]} {
  460. unset state
  461. }
  462. }
  463.  
  464.  
  465. proc http::Connect {token} {
  466. variable $token
  467. upvar 0 $token state
  468. global errorInfo errorCode
  469. if {[eof $state(sock)] ||
  470. [string length [fconfigure $state(sock) -error]]} {
  471. Finish $token "connect failed [fconfigure $state(sock) -error]" 1
  472. } else {
  473. set state(status) connect
  474. fileevent $state(sock) writable {}
  475. }
  476. return
  477. }
  478.  
  479.  
  480. proc http::Write {token} {
  481. variable $token
  482. upvar 0 $token state
  483. set s $state(sock)
  484.  
  485. set done 0
  486. if {[catch {
  487.  
  488. if {[info exists state(-query)]} {
  489.  
  490. puts -nonewline $s  [string range $state(-query) $state(queryoffset)  [expr {$state(queryoffset) + $state(-queryblocksize) - 1}]]
  491. incr state(queryoffset) $state(-queryblocksize)
  492. if {$state(queryoffset) >= $state(querylength)} {
  493. set state(queryoffset) $state(querylength)
  494. set done 1
  495. }
  496. } else {
  497.  
  498. set outStr [read $state(-querychannel) $state(-queryblocksize)]
  499. puts -nonewline $s $outStr
  500. incr state(queryoffset) [string length $outStr]
  501. if {[eof $state(-querychannel)]} {
  502. set done 1
  503. }
  504. }
  505. } err]} {
  506.  
  507. set state(posterror) $err
  508. set done 1
  509. }
  510. if {$done} {
  511. catch {flush $s}
  512. fileevent $s writable {}
  513. fileevent $s readable [list http::Event $token]
  514. }
  515.  
  516.  
  517. if {[string length $state(-queryprogress)]} {
  518. eval $state(-queryprogress) [list $token $state(querylength) $state(queryoffset)]
  519. }
  520. }
  521.  
  522.  
  523. proc http::Event {token} {
  524. variable $token
  525. upvar 0 $token state
  526. set s $state(sock)
  527.  
  528. if {[eof $s]} {
  529. Eof $token
  530. return
  531. }
  532. if {$state(state) eq "header"} {
  533. if {[catch {gets $s line} n]} {
  534. Finish $token $n
  535. } elseif {$n == 0} {
  536. variable encodings
  537. set state(state) body
  538. if {$state(-binary) || ![string match -nocase text* $state(type)]
  539. || [string match *gzip* $state(coding)]
  540. || [string match *compress* $state(coding)]} {
  541. fconfigure $s -translation binary
  542. if {[info exists state(-channel)]} {
  543. fconfigure $state(-channel) -translation binary
  544. }
  545. } else {
  546. set idx [lsearch -exact $encodings  [string tolower $state(charset)]]
  547. if {$idx >= 0} {
  548. fconfigure $s -encoding [lindex $encodings $idx]
  549. }
  550. }
  551. if {[info exists state(-channel)] &&  ![info exists state(-handler)]} {
  552. fileevent $s readable {}
  553. CopyStart $s $token
  554. }
  555. } elseif {$n > 0} {
  556. if {[regexp -nocase {^content-type:(.+)$} $line x type]} {
  557. set state(type) [string trim $type]
  558. regexp -nocase {charset\s*=\s*(\S+)} $type x state(charset)
  559. }
  560. if {[regexp -nocase {^content-length:(.+)$} $line x length]} {
  561. set state(totalsize) [string trim $length]
  562. }
  563. if {[regexp -nocase {^content-encoding:(.+)$} $line x coding]} {
  564. set state(coding) [string trim $coding]
  565. }
  566. if {[regexp -nocase {^([^:]+):(.+)$} $line x key value]} {
  567. lappend state(meta) $key [string trim $value]
  568. } elseif {[string match HTTP* $line]} {
  569. set state(http) $line
  570. }
  571. }
  572. } else {
  573. if {[catch {
  574. if {[info exists state(-handler)]} {
  575. set n [eval $state(-handler) {$s $token}]
  576. } else {
  577. set block [read $s $state(-blocksize)]
  578. set n [string length $block]
  579. if {$n >= 0} {
  580. append state(body) $block
  581. }
  582. }
  583. if {$n >= 0} {
  584. incr state(currentsize) $n
  585. }
  586. } err]} {
  587. Finish $token $err
  588. } else {
  589. if {[info exists state(-progress)]} {
  590. eval $state(-progress)  {$token $state(totalsize) $state(currentsize)}
  591. }
  592. }
  593. }
  594. }
  595.  
  596.  
  597. proc http::CopyStart {s token} {
  598. variable $token
  599. upvar 0 $token state
  600. if {[catch {
  601. fcopy $s $state(-channel) -size $state(-blocksize) -command  [list http::CopyDone $token]
  602. } err]} {
  603. Finish $token $err
  604. }
  605. }
  606.  
  607.  
  608. proc http::CopyDone {token count {error {}}} {
  609. variable $token
  610. upvar 0 $token state
  611. set s $state(sock)
  612. incr state(currentsize) $count
  613. if {[info exists state(-progress)]} {
  614. eval $state(-progress) {$token $state(totalsize) $state(currentsize)}
  615. }
  616. if {[string length $error]} {
  617. Finish $token $error
  618. } elseif {[catch {eof $s} iseof] || $iseof} {
  619. Eof $token
  620. } else {
  621. CopyStart $s $token
  622. }
  623. }
  624.  
  625.  
  626. proc http::Eof {token} {
  627. variable $token
  628. upvar 0 $token state
  629. if {$state(state) eq "header"} {
  630. set state(status) eof
  631. } else {
  632. set state(status) ok
  633. }
  634. set state(state) eof
  635. Finish $token
  636. }
  637.  
  638.  
  639. proc http::wait {token} {
  640. variable $token
  641. upvar 0 $token state
  642.  
  643. if {![info exists state(status)] || [string length $state(status)] == 0} {
  644. vwait $token\(status)
  645. }
  646.  
  647. return $state(status)
  648. }
  649.  
  650.  
  651. proc http::formatQuery {args} {
  652. set result ""
  653. set sep ""
  654. foreach i $args {
  655. append result $sep [mapReply $i]
  656. if {$sep eq "="} {
  657. set sep &
  658. } else {
  659. set sep =
  660. }
  661. }
  662. return $result
  663. }
  664.  
  665.  
  666. proc http::mapReply {string} {
  667. variable http
  668. variable formMap
  669.  
  670.  
  671. if {$http(-urlencoding) ne ""} {
  672. set string [encoding convertto $http(-urlencoding) $string]
  673. return [string map $formMap $string]
  674. }
  675. set converted [string map $formMap $string]
  676. if {[string match "*\[\u0100-\uffff\]*" $converted]} {
  677. regexp {[\u0100-\uffff]} $converted badChar
  678. return -code error  "can't read \"formMap($badChar)\": no such element in array"
  679. }
  680. return $converted
  681. }
  682.  
  683.  
  684. proc http::ProxyRequired {host} {
  685. variable http
  686. if {[info exists http(-proxyhost)] && [string length $http(-proxyhost)]} {
  687. if {![info exists http(-proxyport)] ||  ![string length $http(-proxyport)]} {
  688. set http(-proxyport) 8080
  689. }
  690. return [list $http(-proxyhost) $http(-proxyport)]
  691. }
  692. }
  693.